/* freeircd freeircd.c
 *
 * Created em 01/08/2006
 */

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 199309
#include <errno.h>
#include "misc.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <stdbool.h>
#include <time.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/stat.h>
#include "md5/md5.h"
#include "ggets.h"
#include "types.h"
#include "prototypes.h"
#include "alloc.h"
#include "getinfo.h"

#include "configuration.h"

#include "debug.h"

/*
fork para um processo que so' faz o log!

wildcards no ban e akick

implementar lag

quando for posto chanserv e memoserv apagar TUDO o que tenha referencia ao nick ao apagar (nickserv.c: nickserv_drop/rm_unused_reg)

quando desregistar um nick apagar referencias do canal desse nick e atribuir uma pass aleatoria e dar-la (por memoserv) ao op mais elevado
    (>= 5), e atribuir-lhe estatuto de founder. Se nao existir nenhum apagar o canal.

10 dias antes de desregistar um canal mandar memo ao founder a avisar

no operserv por cena para eviar logs (dcc)
        por cena apagar um nick (mesmo que estaja a ser usado e com never_unreg == true)
        por cena para por o bool never_unreg=true; a um registo de nick (ou false)

nickserv.c:rm_ref_to_nick_reg

ssl

inotify no motd e na lista de ircops || rehash (a actualizar lista de cops e motd)

* TIME
* ACCOUNT
* BURST
* CLEARMODE
* CLOSE
* CNOTICE
* CPRIVMSG
* CREATE
* DESYNCH
* END_OF_BURST
* EOB_ACK
* GET
* GLINE
* MAP
* OPMODE
* POST
* RPING
* RPONG
* SETTIME
* SILENCE
* SVSNICK
* USERIP
* WALLCHOPS
* WALLUSERS
* WALLVOICES
*/

inline void memerror(void *);
void write_sock(CLIENTS *, char *, ...);
void write_sock_already_form(CLIENTS *, char *);
bool nothing_in_buffers(CLIENTS *);
int read_buffer(CLIENTS *, char *, size_t);
int read_sock(CLIENTS *);
void remove_connection(CLIENTS **, CLIENTS *, CHANS *);
int check_ping(CLIENTS *, time_t);
void main_ircd(CLIENTS **, CHANS **, USERREG **, long *, long *, long *, IRCOPS **, MOTD *, fd_set *);
inline bool iscommand(char *);
void load_motd(MOTD **);
void load_ircops(IRCOPS **);
void catch_broken_pipe(int);
void catch_sigterm(int);
static char *get_md5_ip(struct in_addr);
void check_chans(CHANS **, long *);
static void create_config_dir(void);
int main(int, char **);

void destructor(void) __attribute__ ((destructor));
void going_down(veryshort);

#if LOG == 1
char *date;
#endif
CLIENTS **RESERVED_clients_lst;
int *gplisten_s;

inline void memerror(void *p)
{
    if (p == NULL)
    {
            fprintf(stderr,"Memória insuficiente\n");

            exit(EXIT_FAILURE);
    }
}

void write_sock(CLIENTS *client, char *str, ...)
{
    va_list ap;
    char *formated=NULL;
    int errno_cpy;
#if LOG == 1
    bool show=true;
    time_t tm;
#  if LOG_PING_PONG == 0
    char *stmp;
#  endif
    long counter=0;
#endif

    va_start(ap,str);
    vasprintf(&formated,str,ap);
    va_end(ap);

    if (write(client->sd,formated,strlen(formated)) == -1)
    {
            errno_cpy=errno;

            fprintf(stderr,"Erro: write [errno %d] (%s)\n",errno_cpy,
                    (client->nick == NULL) ? NO_NICK_RESPRESENTATION : client->nick);

            client->error=errno_cpy;

            fprintf(stderr,"\t\tReason: %s\n",strerror(errno_cpy));

            free(formated);
            return;
    }

#if LOG == 1
#  if LOG_PING_PONG == 0
    stmp=getoken(formated,1,' ');
    if (stmp != NULL)
            if (compic(stmp,"PING") || compic(stmp,"PONG"))
                    show=false;
    free(stmp);

    stmp=getoken(formated,2,' ');
    if (stmp != NULL)
            if (compic(stmp,"PONG") || compic(stmp,"PING"))
                    show=false;
    free(stmp);
#  endif
    if (show)
    {
            time(&tm);

            strftime(date,32,"%a %b %d %H:%M:%S %Y",localtime(&tm));

            printf("%s   >>> %s: %s: \"",date,inet_ntoa(client->sock.sin_addr),
                                         (client->nick == NULL) ? NO_NICK_RESPRESENTATION: client->nick);

            while (formated[counter] != '\0')
            {
                    switch (formated[counter])
                    {
                            case '\a': printf("\\a"); break;
                            case '\r': printf("\\r"); break;
                            case '\n': printf("\\n"); break;
                            default: putc(formated[counter],stdout);
                    }
                    counter++;
            }
            printf("\"\n");
    }
#endif
    ndb_free(formated);
}

void write_sock_already_form(CLIENTS *client, char *formated)
{
    int errno_cpy;
#if LOG == 1
    bool show=true;
    time_t tm;
#  if LOG_PING_PONG == 0
    char *stmp;
#  endif
    long counter=0;
#endif

    if (write(client->sd,formated,strlen(formated)) == -1)
    {
            errno_cpy=errno;

            fprintf(stderr,"Erro: write [errno %d] (%s)\n",errno_cpy,
                    (client->nick == NULL) ? NO_NICK_RESPRESENTATION : client->nick);

            client->error=errno_cpy;

            fprintf(stderr,"\t\tReason: %s\n",strerror(errno_cpy));

            return;
    }

#if LOG == 1
#  if LOG_PING_PONG == 0
    stmp=getoken(formated,1,' ');
    if (stmp != NULL)
            if (compic(stmp,"PING") || compic(stmp,"PONG"))
                    show=false;
    free(stmp);

    stmp=getoken(formated,2,' ');
    if (stmp != NULL)
            if (compic(stmp,"PONG") || compic(stmp,"PING"))
                    show=false;

    free(stmp);
#  endif
    if (show)
    {
            time(&tm);

            strftime(date,32,"%a %b %d %H:%M:%S %Y",localtime(&tm));

            printf("%s   >>> %s: %s: \"",date,inet_ntoa(client->sock.sin_addr),
                                         (client->nick == NULL) ? NO_NICK_RESPRESENTATION: client->nick);

            while (formated[counter] != '\0')
            {
                    switch (formated[counter])
                    {
                            case '\a': printf("\\a"); break;
                            case '\r': printf("\\r"); break;
                            case '\n': printf("\\n"); break;
                            default: putc(formated[counter],stdout);
                    }
                    counter++;
            }
            printf("\"\n");
    }
#endif
    return;
}

bool nothing_in_buffers(CLIENTS *client)
{
    bool ret=true;

    while (client != NULL)
    {
            if (client->buffinfo.size)
            {
                    ret=false;
                    break;
            }

            client=client->next;
    }

    return ret;
}

/*Lê bytes bytes, se os tiver em buffer do buffer senão vai ler-los com o read()
Grava em buf bytes bytes, ou ate' encontrar um '\n',
se encontrar um '\n' grava tudo que sobre no buffer*/
int read_buffer(CLIENTS *client, char *buf, size_t bytes)
{
    int bbuf;
    int readed=0;
    int counter=0;
    bool ret=false;
    ssize_t sreaded;
    char *cpy_from;

    if ((bbuf=client->buffinfo.size)) /*Vamos ler do buffer*/
    {
            while (bbuf)
            {
                    buf[readed++]=client->buffinfo.buff[counter++];

                    bbuf--;

                    if (buf[readed-1] == '\n'
                        || readed == bytes)
                    {
                            ret=true;
                            break;
                    }
            }

            if (!(client->buffinfo.size=bbuf))
                    memcpy(client->buffinfo.buff,client->buffinfo.buff+readed,bbuf);

            if (ret)
                    return readed;
    }

    /*Vamos agora ler da socket (aqui o buffer ja' esta' vazio)*/

    sreaded=read(client->sd,buf+readed,bytes-readed);

    if (sreaded <= 0)
            return sreaded;

    /*Se houver um '\n' e esse '\n' não for o ultimo copia o que se seguir para o buffer (que esta' vazio)*/
    if ((cpy_from=memchr(buf+readed,'\n',sreaded)) != NULL
         && cpy_from != buf+readed+sreaded-1)
    {
            memcpy(client->buffinfo.buff,cpy_from+1,buf+readed+sreaded-1-cpy_from);

            client->buffinfo.size=buf+readed+sreaded-1-cpy_from;

            return cpy_from-buf+1;
    }

    return readed+sreaded;
}

int read_sock(CLIENTS *client)
{
    char buff[READ_BUFFER_SIZE];
    int errno_cpy;
    ssize_t r_ret;

    if (client->msg_ok)
            return 0;

    if (client->msg == NULL)
    {
            client->msg=malloc(client->size=DEFAULT_RECV_STR_SIZE);
            memerror(client->msg);
    }

    if ((r_ret=read_buffer(client,buff,READ_BUFFER_SIZE)) != 0)
    {
            if (r_ret == -1)
            {
                    errno_cpy=errno;

                    if (errno_cpy == EWOULDBLOCK)
                            return 0;

                    fprintf(stderr,"Erro: read [errno %d] (%s)\n",errno_cpy,
                                                                  (client->nick == NULL) ? NO_NICK_RESPRESENTATION : client->nick);

                    fprintf(stderr,"\t\tReason: %s\n",strerror(errno_cpy));

                    return errno_cpy;
            }

            if (client->counter+r_ret > RECV_STR_LEN_MAX)
            {
                    client->msg_ok=false;
                    return -1;
            }

            if (client->counter+r_ret >= client->size_msg-1)
            {
                    client->size_msg+=r_ret+1;
                    client->msg=realloc(client->msg,client->size_msg);
                    memerror(client->msg);
            }

            memcpy(client->msg+client->counter,buff,r_ret);

            client->counter+=r_ret;

            if (client->msg[client->counter-1] == '\n')
            {
                    client->msg_ok=true;
                    if (client->counter >= 2 && client->msg[client->counter-2] == '\r')
                            client->msg[client->counter-2]='\0';
                    else
                            client->msg[client->counter-1]='\0';
            }
   }
   else /*EOF*/
            return -2;

    return 0;
}

void remove_connection(CLIENTS **ppclients, CLIENTS *to_rm, CHANS *chans)
{
    struct user_chans_list *u_chs_tmp;
    struct chan_users_list *user_in_chan;

    rm_client(ppclients,to_rm);

    while (to_rm->chans != NULL)
    {
            u_chs_tmp=to_rm->chans;
            rm_channel_in_user_list(&to_rm->chans,to_rm->chans);

            user_in_chan=get_user_in_chan(u_chs_tmp->chan,to_rm->nick);

            rm_user_in_channel_list(&u_chs_tmp->chan->users,user_in_chan);

            free(u_chs_tmp);
    }

    while (chans != NULL)
    {
            if (chans->reg != NULL)
            {
                    struct access_users *auser;
                    auser=get_user_access(chans->reg->ausers,to_rm->nick);
                    auser->founder_access=false;
            }
            chans=chans->next;
    }

    if (to_rm->reg != NULL && to_rm->reg->status == USER_STATUS_IDENTIFIED)
    {
            time_t now;

            time(&now);
            to_rm->reg->online_time+=now-to_rm->reg->last_time;
            to_rm->reg->last_time=now;
            to_rm->reg->status=USER_STATUS_OFFLINE;
            to_rm->reg->user=NULL;

            free(to_rm->reg->last_mask);
            to_rm->reg->last_mask=malloc(strlen(USER_NAME)+strlen(to_rm->md5_ip)+2);
            memerror(to_rm->reg->last_mask);
            sprintf(to_rm->reg->last_mask,"%s!%s",USER_NAME,to_rm->md5_ip);

            save_userreg(to_rm->reg);
    }

    if (to_rm->msg != NULL)
            memset(to_rm->msg,'\0',strlen(to_rm->msg));

    if (to_rm->nick != NULL)
            memset(to_rm->nick,'\0',strlen(to_rm->nick));

    if (to_rm->name != NULL)
            memset(to_rm->name,'\0',strlen(to_rm->name));

    if (to_rm->nick != NULL)
            memset(to_rm->nick,'\0',strlen(to_rm->nick));

    if (to_rm->away != NULL)
            memset(to_rm->away,'\0',strlen(to_rm->away));

    memset(to_rm->md5_ip,'\0',strlen(to_rm->md5_ip));
    memset(to_rm->buffinfo.buff,'\0',READ_BUFFER_SIZE);

    free(to_rm->msg);
    free(to_rm->nick);
    free(to_rm->name);
    free(to_rm->away);
    free(to_rm->md5_ip);
    free(to_rm->buffinfo.buff);
    free(to_rm);
}

int check_ping(CLIENTS *client, time_t tm)
{
    if (client->sent_ping != 0) /*Está á espera do pong*/
    {
            /*Verifica time-out*/
            if (tm > client->sent_ping+PING_TIMEOUT)
            {
                    write_sock(client,"ERROR :Closing Link: %s[%s] by %s (Ping Timeout)\r\n",
                               (client->nick == NULL) ? NO_NICK_RESPRESENTATION : client->nick,SERVER_NAME,SERVER_NAME);

                    return 1;
            }
    }
    else if (tm > client->next_ping)
    {
                    client->sent_ping=tm;

                    write_sock(client,"PING :%ld\r\n",(long)tm);
    }
    return 0;
}

void main_ircd(CLIENTS **ppclients, CHANS **ppchans, USERREG **ppusers_regs, long *users, long *ircops, long *channels, IRCOPS **ircops_lst,
           MOTD *motd, fd_set *prd)
{
    CLIENTS *pclients=*ppclients;
    CLIENTS *tmp;
    char *command;
    int status;
    time_t now;
#if LOG == 1
    time_t tm;
#  if LOG_PING_PONG == 0
    char *stmp;
#  endif
#endif

    time(&now);
    while (pclients != NULL)
    {
            /*Le mensagens*/
            if (FD_ISSET(pclients->sd,prd) || !pclients->buffinfo.size)
            {
                    status=read_sock(pclients);

                    if (status != 0) /*Cliente out*/
                    {
                            /*:coiso!~orium@irc.ptsida.org QUIT :Quit: reason*/

                            switch (status)
                            {
                                    case -1:
                                    {
                                                    send_to_all(pclients,":%s!%s@%s QUIT :Quit: Line too long\r\n",
                                                                         pclients->nick,USER_NAME,pclients->md5_ip);
                                                    write_sock(pclients,"ERROR :Closing Link: %s[%s] by %s (Line too long)\r\n",
                                                                     (pclients->nick == NULL) ? NO_NICK_RESPRESENTATION : pclients->nick,
                                                                     SERVER_NAME,SERVER_NAME);
                                                    close(pclients->sd);

                                                    break;
                                    }
                                    case -2:
                                    {
                                            send_to_all(pclients,":%s!%s@%s QUIT :Quit: Read error: EOF from client\r\n",
                                                                 pclients->nick,USER_NAME,pclients->md5_ip);
                                            break;
                                    }
                                    default:
                                            send_to_all(pclients,":%s!%s@%s QUIT :Quit: Read error: %s\r\n",
                                                                 pclients->nick,USER_NAME,pclients->md5_ip,strerror(status));
                            }

                            tmp=pclients;
                            pclients=pclients->next;
                            remove_connection(ppclients,tmp,*ppchans);
                            (*users)--;
                            check_chans(ppchans,channels);
                            continue;
                    }
            }

            status=check_ping(pclients,now);
            if (status != 0) /*Ping timeout*/
            {
                    if (pclients->nick != NULL)
                    {
                            send_to_all(pclients,":%s!%s@%s QUIT :Quit: Ping Timeout\r\n",pclients->nick,USER_NAME,pclients->md5_ip);
                            fprintf(stderr,"Ping Time out: %s\n",pclients->nick);
                    }
                    else
                            printf("Ping Time out from %s\n",inet_ntoa(pclients->sock.sin_addr));

                    close(pclients->sd);
                    tmp=pclients;
                    pclients=pclients->next;
                    remove_connection(ppclients,tmp,*ppchans);
                    (*users)--;
                    check_chans(ppchans,channels);
                    continue;
            }

            if (pclients->do_welcome == 2 && pclients->nick != NULL)
            {
                    char *stmp_msg;
                    do_welcome(pclients,*users,*ircops,*channels,motd);

                    if (pclients->reg != NULL) if (pclients->reg->status == USER_STATUS_TO_IDENTIFY)
                            write_sock(pclients,":%s!%s@%s NOTICE %s :Nick registado e protegido!"
                                            " Se e' teu escreve /%s identify <password>."
                                            " Tens %d segs para o fazer ou mudar de nick!\r\n",
                                            NICKSERV_NAME,USER_NAME,SERVER_NAME,pclients->nick,NICKSERV_NAME,
                                            pclients->reg->kill_timeout);

                    stmp_msg=pclients->msg;

                    pclients->msg=malloc(32);
                    memerror(pclients->msg);

                    if (pclients->reg == NULL)
                            strcpy(pclients->msg,"JOIN #unregisted");
                    else if (pclients->reg->status == USER_STATUS_TO_IDENTIFY)
                            strcpy(pclients->msg,"JOIN #unidentified");
#if FORCE_JOIN_PUTEDO == 1
                    else
                            strcpy(pclients->msg,"JOIN #putedo");
#endif

                    join(ppclients,pclients,channels,ppchans);
                    free(pclients->msg);
                    pclients->msg=stmp_msg;
            }

            if (pclients->reg != NULL && pclients->reg->status == USER_STATUS_TO_IDENTIFY)
            {
                    if (now > pclients->reg->ident_timeout)
                    {
                            if (!pclients->ircop)
                            {
                                    write_sock(pclients,":%s!%s@%s KILL killed :%s (Identify Timeout)\r\n",
                                            pclients->nick,USER_NAME,pclients->md5_ip,NICKSERV_NAME);


                                    write_sock(pclients,"ERROR :Closing Link: %s[%s@%s] by %s (Killed (%s (Identify Timeout)))\r\n",
                                            pclients->nick,USER_NAME,pclients->md5_ip,NICKSERV_NAME,NICKSERV_NAME);


                                    send_to_all(pclients,":%s!%s@%s QUIT :Killed (%s (Identify Timeout))\r\n",
                                            pclients->nick,USER_NAME,pclients->md5_ip,NICKSERV_NAME);

                                    printf("Indentify timeout: %s (%s)\n",pclients->nick,inet_ntoa(pclients->sock.sin_addr));

                                    close(pclients->sd);
                                    tmp=pclients;
                                    pclients=pclients->next;
                                    remove_connection(ppclients,tmp,*ppchans);
                                    (*users)--;
                                    check_chans(ppchans,channels);
                                    continue;
                            }
                            else
                            {
                                    write_sock(pclients,":%s!%s@%s NOTICE %s :Atenção: Nick %s esta' registado\r\n",
                                                        NICKSERV_NAME,USER_NAME,SERVER_NAME,pclients->nick,pclients->nick);
                                    write_sock(pclients,":%s!%s@%s NOTICE %s :Se o nick e' teu identifica-te!\r\n",
                                                        NICKSERV_NAME,USER_NAME,SERVER_NAME,pclients->nick);
                                    pclients->reg->status=USER_STATUS_IDENTIFIED;
                            }
                    }
            }

            if (pclients->ident_failed >= LIMIT_FAILED_IDENT)
            {
                    write_sock(pclients,":%s!%s@%s KILL killed :%s (Identify failed limit (%d))\r\n",
                                    pclients->nick,USER_NAME,pclients->md5_ip,SERVER_NAME,pclients->ident_failed);


                    write_sock(pclients,"ERROR :Closing Link: %s[%s@%s] by %s (Killed (%s (Identify failed limit (%d))))\r\n",
                                    pclients->nick,USER_NAME,pclients->md5_ip,SERVER_NAME,NICKSERV_NAME,pclients->ident_failed);


                    send_to_all(pclients,":%s!%s@%s QUIT :Killed (%s (Identify failed limit (%d)))\r\n",
                                    pclients->nick,USER_NAME,pclients->md5_ip,SERVER_NAME,pclients->ident_failed);

                    printf("Indentify failed limit (%d): %s (%s)\n",pclients->ident_failed,pclients->nick,
                                                                    inet_ntoa(pclients->sock.sin_addr));

                    close(pclients->sd);
                    tmp=pclients;
                    pclients=pclients->next;
                    remove_connection(ppclients,tmp,*ppchans);
                    (*users)--;
                    check_chans(ppchans,channels);
                    continue;
            }

            if (pclients->msg_ok)
            {
                    /*Processa a mensagem*/
#if LOG == 1
                    time(&tm);

                    strftime(date,32,"%a %b %d %H:%M:%S %Y",localtime(&tm));

#  if LOG_PING_PONG == 1
                    printf("%s   <<< %s: %s: \"%s\\r\\n\"\n",date,inet_ntoa(pclients->sock.sin_addr),
                                                   (pclients->nick == NULL) ? NO_NICK_RESPRESENTATION : pclients->nick,pclients->msg);
#  else
                    stmp=getoken(pclients->msg,1,' ');
                    if (stmp != NULL)
                            if (!compic(stmp,"PING") && !compic(stmp,"PONG"))
                                    printf("%s   <<< %s: %s: \"%s\\r\\n\"\n",date,inet_ntoa(pclients->sock.sin_addr),
                                                    (pclients->nick == NULL) ? NO_NICK_RESPRESENTATION : pclients->nick,pclients->msg);
                    free(stmp);
#  endif
#endif
                    time(&pclients->last_move);
                    command=getoken(pclients->msg,1,' ');

                    if (command != NULL)
                    {
                            if (iscommand(command))
                            {
                                    if (compic(command,"NICK"))
                                    {
                                            char *nick_cpy=NULL;

                                            if (pclients->nick != NULL)
                                            {
                                                    nick_cpy=malloc(strlen(pclients->nick)+1);
                                                    memerror(nick_cpy);
                                                    strcpy(nick_cpy,pclients->nick);
                                            }

                                            if (nick(ppclients,pclients,ppchans,channels) && !compic(nick_cpy,pclients->nick))
                                            {
                                                    char *stmp_msg;

                                                    if (pclients->reg != NULL && pclients->reg->status == USER_STATUS_IDENTIFIED)
                                                    {
                                                            time_t now;

                                                            time(&now);
                                                            pclients->reg->online_time+=now-pclients->reg->last_time;
                                                            pclients->reg->last_time=now;
                                                            pclients->reg->status=USER_STATUS_OFFLINE;
                                                            pclients->reg->user=NULL;

                                                            free(pclients->reg->last_mask);
                                                            pclients->reg->last_mask=
                                                                                    malloc(strlen(USER_NAME)+strlen(pclients->md5_ip)+2);
                                                            memerror(pclients->reg->last_mask);
                                                            sprintf(pclients->reg->last_mask,"%s!%s",USER_NAME,pclients->md5_ip);

                                                            save_userreg(pclients->reg);
                                                    }

                                                    pclients->ident_failed=0;

                                                    pclients->reg=get_user_reg(pclients,ppusers_regs,*ppchans);

                                                    stmp_msg=pclients->msg;

                                                    pclients->msg=malloc(32);
                                                    memerror(pclients->msg);

                                                    if (pclients->reg == NULL)
                                                            strcpy(pclients->msg,"JOIN #unregisted");
                                                    else if (pclients->reg->status == USER_STATUS_TO_IDENTIFY)
                                                            strcpy(pclients->msg,"JOIN #unidentified");
#if FORCE_JOIN_PUTEDO == 1
                                                    else
                                                            strcpy(pclients->msg,"JOIN #putedo");
#endif
                                                    if (pclients->do_welcome == 0)
                                                            join(ppclients,pclients,channels,ppchans);

                                                    free(pclients->msg);
                                                    pclients->msg=stmp_msg;
                                            }

                                            free(nick_cpy);
                                    }
                                    if (compic(command,"USER")) user(pclients);
                                    if (compic(command,"PONG")) pong(pclients);
                                    if (compic(command,"HELP")) help(pclients);

                                    if (pclients->nick != NULL)
                                    {
                                            if (compic(command,"PING")) ping(pclients);
                                            if (compic(command,"USERHOST")) userhost(ppclients,pclients);
                                            if (compic(command,"WHOIS")) whois(ppclients,pclients);
                                            if (compic(command,"PRIVMSG")) privmsg(ppclients,pclients);
                                            if (compic(command,"NOTICE")) notice(ppclients,pclients);
                                            if (compic(command,"VERSION")) show_version(pclients);
                                            if (compic(command,"INFO")) info(pclients);
                                            if (compic(command,"WHO")) who(pclients);
                                            if (compic(command,"NAMES")) names(pclients);
                                            if (compic(command,"OPER")) oper(pclients,ircops,*ircops_lst);
                                            if (compic(command,"PART")) part(pclients,ppchans,false,channels,false);
                                            if (compic(command,"KICK")) kick(pclients,ppchans,ppclients,channels);
                                            if (compic(command,"LIST")) list(pclients,*ppchans);
                                            if (compic(command,"TOPIC")) topic(pclients,*ppchans);
                                            if (compic(command,"KILL")) kill_(ppclients,pclients);
                                            if (compic(command,"JOIN")) join(ppclients,pclients,channels,ppchans);
                                            if (compic(command,"ISON")) ison(ppclients,pclients);
                                            if (compic(command,"AWAY")) away(pclients);
                                            if (compic(command,"INVITE")) invite(pclients,*ppclients);
                                            if (compic(command,"DIE")) die(pclients,ppclients,*ircops_lst);
                                            if (compic(command,"LUSERS")) show_lusers(pclients,*users,*ircops,*channels);
                                            if (compic(command,"MOTD")) show_motd(pclients,motd);
                                            if (compic(command,"MODE")) mode(pclients,*ppchans,ircops);
                                            if (compic(command,"WALLOPS")) wallops(pclients,*ppclients);

                                            if (compic(command,"QUIT"))
                                            {
                                                    close(pclients->sd);

                                                    /*:coiso!~orium@irc.ptsida.org QUIT :coiso*/
                                                    if (tknum(pclients->msg,' ') < 2)
                                                            send_to_all(pclients,":%s!%s@%s QUIT :%s\r\n",
                                                                    pclients->nick,USER_NAME,pclients->md5_ip,pclients->nick);
                                                    else
                                                    {
                                                            char *qmsg;
                                                            qmsg=get_irc_str(pclients->msg,2);
                                                            if (qmsg == NULL)
                                                                    send_to_all(pclients,":%s!%s@%s QUIT :%s\r\n",
                                                                            pclients->nick,USER_NAME,pclients->md5_ip,pclients->nick);
                                                            else
                                                                    send_to_all(pclients,":%s!%s@%s QUIT :Quit: %s\r\n",
                                                                            pclients->nick,USER_NAME,pclients->md5_ip,qmsg);
                                                            free(qmsg);
                                                    }

                                                    tmp=pclients;
                                                    pclients=pclients->next;
                                                    remove_connection(ppclients,tmp,*ppchans);
                                                    (*users)--;
                                                    free(command);
                                                    check_chans(ppchans,channels);
                                                    continue;
                                            }

                                            if (compic(command,NICKSERV_NAME))
                                                    nickserv(pclients,ppusers_regs,ppchans,channels,ppclients,users);

                                            if (compic(command,CHANSERV_NAME))
                                                    chanserv(pclients,ppchans,*ppusers_regs,ppclients,channels);

                                    }
                                    else if (!compic(command,"NICK") && !compic(command,"USER") && !compic(command,"PONG") &&
                                             !compic(command,"PASS") && !compic(command,"HELP"))
                                            /*:server 451 ? command :You have not registered*/
                                            write_sock(pclients,":%s 451 ? %s :You have not registered\r\n",SERVER_NAME,command);
                            }
                            else
                                    /*:server 421 nick command :Unknown command*/
                                    write_sock(pclients,":%s 421 %s %s :Unknown command\r\n",SERVER_NAME,
                                                        (pclients->nick == NULL) ? NO_NICK_RESPRESENTATION : pclients->nick,command);
                            free(command);
                    }

                    pclients->msg_ok=false;
                    pclients->size_msg=0;
                    pclients->counter=0;

                    memset(pclients->msg,'\0',strlen(pclients->msg));
                    free(pclients->msg);
                    pclients->msg=NULL;
            }

            if (pclients->error != 0)
            {
                    if (pclients->error < 0 && !pclients->killed)
                            send_to_all(pclients,":%s!%s@%s QUIT :Quit: %s\r\n",
                                        pclients->nick,USER_NAME,pclients->md5_ip,strerror(pclients->error));

                    tmp=pclients;
                    pclients=pclients->next;
                    remove_connection(ppclients,tmp,*ppchans);
                    (*users)--;
                    check_chans(ppchans,channels);
                    continue;
            }

            if (!(pclients->do_welcome == 2 && pclients->nick != NULL))
                    pclients=pclients->next;
    }
}
/*
:irc.ptsida.org 352 coiso #chan ~orium irc.ptsida.org irc.ptsida.org orium H :0 Diogo Sousa
:irc.ptsida.org 352 coiso #chan ~orium irc.ptsida.org irc.ptsida.org coiso H@ :0 Diogo Sousa
:irc.ptsida.org 315 coiso #chan :End of /WHO list.
*/
__attribute__((always_inline))
inline bool iscommand(char *command)
{
    if (compic(command,"NICK")) return true;
    if (compic(command,"PING")) return true;
    if (compic(command,"PONG")) return true;
    if (compic(command,"PASS")) return true; /*fake*/
    if (compic(command,"MODE")) return true;
    if (compic(command,"USER")) return true;
    if (compic(command,"WHO")) return true;
    if (compic(command,"NAMES")) return true;
    if (compic(command,"QUIT")) return true;
    if (compic(command,"USERHOST")) return true;
    if (compic(command,"PRIVMSG")) return true;
    if (compic(command,"NOTICE")) return true;
    if (compic(command,"WHOIS")) return true;
    if (compic(command,"INFO")) return true;
    if (compic(command,"TOPIC")) return true;
    if (compic(command,"PART")) return true;
    if (compic(command,"KICK")) return true;
    if (compic(command,"VERSION")) return true;
    if (compic(command,"JOIN")) return true;
    if (compic(command,"LIST")) return true;
    if (compic(command,"OPER")) return true;
    if (compic(command,"ISON")) return true;
    if (compic(command,"KILL")) return true;
    if (compic(command,"AWAY")) return true;
    if (compic(command,"LUSERS")) return true;
    if (compic(command,"MOTD")) return true;
    if (compic(command,"HELP")) return true;
    if (compic(command,"INVITE")) return true;
    if (compic(command,"DIE")) return true;
    if (compic(command,"MODE")) return true;
    if (compic(command,"WALLOPS")) return true;

    if (compic(command,NICKSERV_NAME)) return true;
    if (compic(command,CHANSERV_NAME)) return true;

    return false;
}

void load_motd(MOTD **motd)
{
    FILE *motd_fp;
    MOTD *motd_lst=*motd;
    char *line;
    char *filename;

    if (*motd != NULL)
    {
            MOTD *tmp=*motd;
            /*Liberta todo o motd*/

            while (motd_lst != NULL)
            {
                    tmp=motd_lst;
                    motd_lst=motd_lst->next;
                    free(tmp->line);
                    free(tmp);
            }

            motd_lst=*motd=NULL;
    }

    asprintf(&filename,"%s/.%s/%s",getenv("HOME"),FREEIRCD_NAME,MOTD_FILE);

    if (filename == NULL)
            return;

    motd_fp=fopen(filename,"r");

    ndb_free(filename);
    if (motd_fp == NULL)
            return;

    /*Aloca o motd*/
    while (fggets(&line,motd_fp) == GGETS_OK)
    {
            if (*motd == NULL)
            {
                    motd_lst=*motd=malloc(sizeof(*motd_lst));
                    memerror(*motd);
            }
            else
            {
                    motd_lst->next=malloc(sizeof(*(motd_lst->next)));
                    memerror(motd_lst->next);
                    motd_lst=motd_lst->next;
            }

            motd_lst->line=line;
    }
    motd_lst->next=NULL;

    fclose(motd_fp);
}

void load_ircops(IRCOPS **pircops)
{
    FILE *ircops_fp;
    IRCOPS *ircops=*pircops;
    int counter;
    char *filename;
    char *line;
    char *user;
    char *hash;
    unsigned char tmp[3];

    tmp[2]='\0';

    asprintf(&filename,"%s/.%s/ircops",getenv("HOME"),FREEIRCD_NAME);

    memerror(filename);

    ircops_fp=fopen(filename,"r");

    ndb_free(filename);

    while (*pircops != NULL)
    {
            ircops=*pircops;
            rm_ircop_list(pircops,ircops);

            free(ircops);
    }

    if (ircops_fp == NULL)
            return;

    while (fggets(&line,ircops_fp) == GGETS_OK)
    {
            if (strlen(line) == 0)
            {
                    free(line);
                    continue;
            }

            user=getoken(line,1,':');
            hash=getoken(line,2,':');

            free(line);

            if (strlen(user) == 0 || strlen(hash) != 32)
            {
                    free(user);
                    free(hash);
            }

            ircops=alloc_ircop_list(pircops);

            ircops->username=user;

            counter=0;

            while (counter < 16)
            {
                    tmp[0]=hash[counter*2];
                    tmp[1]=hash[counter*2+1];

                    ircops->md5_hash[counter]=(unsigned char)strtol((char *)tmp,NULL,16);

                    counter++;
            }

            free(hash);
    }

    fclose(ircops_fp);
}

void catch_broken_pipe(int sig_num)
{
    raise(SIGCONT);
    signal(SIGPIPE, catch_broken_pipe);
}

void catch_sigterm(int sig_num)
{
    going_down(1);
    raise(SIGKILL);
}

static char *get_md5_ip(struct in_addr addr) /*Gera e aloca um md5 do ip*/
{
    MD5_CTX state;
    unsigned char digest[16];
    unsigned char tmp[6]; /*sizeof(addr)+2, 281.474.976.710.656 combinações*/
    char *md5_ip;
    veryshort *ip;

    ip=(veryshort *)&addr;
    tmp[0]='#';
    tmp[1]=ip[0];
    tmp[2]=ip[1];
    tmp[3]='$';
    tmp[4]=ip[2];
    tmp[5]=ip[3];

    MD5Init(&state);
    MD5Update(&state,tmp,6);
    MD5Final(digest,&state);

    md5_ip=malloc(33);
    memerror(md5_ip);

    snprintf(md5_ip,33,"%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],
                                                          digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],
                                                          digest[13],digest[14],digest[15]);

    return md5_ip;
}

void check_chans(CHANS **chans_lst, long *channels)
{
    CHANS *pchans=*chans_lst;
    CHANS *next_one;
    struct ban_list *bans;

    while (pchans != NULL)
    {
            if (pchans->reg == NULL && pchans->users == NULL) /*Não esta' registado e esta' vazio*/
            {
                    (*channels)--;

                    next_one=pchans->next;

                    rm_channel(chans_lst,pchans);

                    while (pchans->bans != NULL)
                    {
                            bans=pchans->bans;

                            rm_ban_list(&pchans->bans,bans);

                            free(bans->nick);
                            free(bans->md5_ip);
                            free(bans->banned_by);
                            free(bans);
                    }

                    free(pchans->name);
                    free(pchans->topic);
                    free(pchans->topic_set_by);
                    free(pchans->modes.key);
                    free(pchans);

                    pchans=next_one;
                    continue;
            }

            pchans=pchans->next;
    }
}

static void create_config_dir(void)
{
    char *dir;

    asprintf(&dir,"%s/.%s",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/nickserv",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/nickserv/registos",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/nickserv/ajoins",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/nickserv/notas",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/chanserv",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/chanserv/registos",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/chanserv/ausers",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/memoserv",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/operserv",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);

    asprintf(&dir,"%s/.%s/logs",getenv("HOME"),FREEIRCD_NAME);
    mkdir(dir,0700);
    ndb_free(dir);
}

int main(int argc, char **argv)
{
    /* init all structures and variables */
    CLIENTS *clients=NULL;
    CLIENTS *pclients=NULL;
    USERREG *reg_users=NULL;
    CHANS *chans=NULL;
    MOTD *motd=NULL;
    IRCOPS *ircops_lst=NULL;
    long users=0;
    long ircops=0;
    long channels=0;
    int listen_s;
    int sockd_tmp;
    int fd_max;
    short port = DEFAULT_PORT;
    socklen_t size_tmp;
    struct sockaddr_in local;
    struct sockaddr_in client_tmp;
    fd_set rd;
    fd_set wr;
    fd_set er;
    struct timeval select_timeout;
#if LOG == 1
    unsigned long long connections_received=0;
    time_t tm;
#endif
    time_t now;
    time_t next_act_motd;

    RESERVED_clients_lst=&clients;

    gplisten_s=&listen_s;

    /* create freeirc directory under home user dir */
    create_config_dir();

    /* load motd and ircops list from config file */
    load_motd(&motd);
    load_ircops(&ircops_lst);

    /* catch signals */
    if (signal(SIGPIPE, &catch_broken_pipe) == SIG_ERR)
        fprintf(stderr,"ERROR: received SIGPIPE signal\n");
#if MEM_DEBUG
    if (signal(SIGABRT, &debug_abort) == SIG_ERR)
        fprintf(stderr,"ERROR: received SIGABRT signal\n");
#endif

    /* get server port from argv */
    if (argc == 2)
        if (isstrnum(argv[1]))
            port=atoi(argv[1]);

    /* init all socket related stuff */
    bzero(&local,sizeof(local));
    local.sin_family=AF_INET;
    local.sin_port=htons(port);
    local.sin_addr.s_addr=INADDR_ANY;

    bzero(&(local.sin_zero),8);

    /* create a socket */
    listen_s=socket(AF_INET, SOCK_STREAM, 0);
    if (listen_s == -1)
    {
        perror("socket()");
        return EXIT_FAILURE;
    }

    /* bind the socket to the port */
    if (bind(listen_s, (struct sockaddr *)&local,sizeof(struct sockaddr)) == -1)
    {
        short counter=0;

        fprintf(stderr,"ERROR: could not bind the socket\n");

        while (bind(listen_s,(struct sockaddr *)&local,sizeof(struct sockaddr)) == -1)
        {
            if (counter >= BIND_MAX_RETRIES)
            {
                perror("bind()");
                return EXIT_FAILURE;
            }
            sleep(1); /* sleep 1 second and try again */
            counter++;
        }
    }

    /* listen to the specified port in socket */
    if (listen(listen_s,DEFAULT_MAX_QUEUE_USERS) == -1)
    {
        perror("listen()");
        return EXIT_FAILURE;
    }

    printf("%s version %s\n", FREEIRCD_NAME, FREEIRCD_VERSION);
    printf("listening on port %ld\n\n",port);

    time(&now);

    next_act_motd=now;
    next_act_motd+=ACT_MOTD_FREQ;

#if LOG == 1
    date=malloc(64);
    memerror(date);
#endif

    if (signal(SIGTERM, &catch_sigterm) == SIG_ERR)
        fprintf(stderr,"ERROR: received SIGTERM signal\n");

    load_userreg(&reg_users);

    while (1)
    {
        if (now > next_act_motd)
        {
            load_motd(&motd);
            load_ircops(&ircops_lst);

            check_unused_reg(&reg_users,chans);
            next_act_motd=now+ACT_MOTD_FREQ;
        }

        if (nothing_in_buffers(clients))
        {
            FD_ZERO(&rd);
            FD_ZERO(&wr);
            FD_ZERO(&er);

            select_timeout.tv_sec=SELECT_TIMEOUT;
            select_timeout.tv_usec=0;

            fd_max=listen_s;

            FD_SET(listen_s,&rd);

            pclients=clients;

            while (pclients != NULL)
            {
                FD_SET(pclients->sd,&rd);

                if (pclients->sd > fd_max)
                    fd_max=pclients->sd;

                    pclients=pclients->next;
                }

                if (select(fd_max+1,&rd,&wr,&er,&select_timeout) < 0)
                {
                    if (errno == EINTR)
                        continue;

                    perror("select()");
                    return EXIT_FAILURE;
                }
        }

        time(&now);

        /* main freeircd loop */
        main_ircd(&clients,&chans,&reg_users,&users,&ircops,&channels,&ircops_lst,motd,&rd);

            if (FD_ISSET(listen_s,&rd))
            {
                size_tmp=sizeof(struct sockaddr_in);
                sockd_tmp=accept(listen_s,(struct sockaddr *)&client_tmp,&size_tmp);
                if (sockd_tmp == -1)
                {
                    perror("accept()");

                    return EXIT_FAILURE;
                }

                if (users >= MAX_USERS && MAX_USERS != -1)
                {
                    fprintf(stderr,"Connection rejected: %ld users\n",users);
                    close(sockd_tmp);
                    continue;
                }

                users++;
#if LOG == 1
                time(&tm);

                strftime(date,32,"%a %b %d %H:%M:%S %Y",localtime(&tm));

                connections_received++;
                printf("%s   New connection #%llu from %s\n",date,connections_received,inet_ntoa(client_tmp.sin_addr));
#endif

                pclients=alloc_client(&clients);

                    pclients->nick=NULL;
                    pclients->msg=NULL;
                    pclients->name=NULL;
                    pclients->chans=NULL;
                    pclients->away=NULL;
                    time(&pclients->sign_on);
                    pclients->next_ping=pclients->sign_on+PING_FREQUENCY;
                    pclients->last_move=pclients->sign_on;
                    pclients->do_welcome=1;
                    pclients->ircop=false;
                    pclients->sent_ping=0;
                    pclients->msg_ok=false;
                    pclients->killed=false;
                    pclients->error=0;
                    pclients->size=0;
                    pclients->counter=0;
                    pclients->sd=sockd_tmp;
                    pclients->sock=client_tmp;
                    pclients->size=size_tmp;
                    pclients->reg=NULL;
                    pclients->ident_failed=0;

                    pclients->md5_ip=get_md5_ip(pclients->sock.sin_addr);

                    pclients->buffinfo.size=0;
                    pclients->buffinfo.buff=malloc(READ_BUFFER_SIZE);
                    memerror(pclients->buffinfo.buff);

                    if (fcntl(pclients->sd,F_SETFL,O_NONBLOCK) == -1)
                    {
                            perror("fcntl()");
                            return EXIT_FAILURE;
                    }

            }
    }

    return EXIT_SUCCESS;
}

__attribute__ ((destructor))
void destructor(void)
{
    going_down(0);
}

void going_down(veryshort flag)
{
    CLIENTS *pmclient=*RESERVED_clients_lst;
    time_t now;

    if (flag == 0)
    {
            fprintf(stderr,"Shutdown: Internal exit()\n");
            while (pmclient != NULL)
            {
                    write_sock(pmclient,":%s NOTICE * :*** Notice -- shutdown: Internal exit()\r\n",SERVER_NAME);

                    pmclient=pmclient->next;
            }
    }
    else
    {
            printf("Shutdown: SIGTERM\n");
            while (pmclient != NULL)
            {
                    write_sock(pmclient,":%s NOTICE * :*** Notice -- shutdown: SIGTERM\r\n",SERVER_NAME);

                    pmclient=pmclient->next;
            }
    }

    pmclient=*RESERVED_clients_lst;
    time(&now);
    while (pmclient != NULL)
    {
            write_sock(pmclient,":%s NOTICE * :*** Notice -- Server terminating.\r\n",SERVER_NAME);
            close(pmclient->sd);

            if (pmclient->reg != NULL && pmclient->reg->status == USER_STATUS_IDENTIFIED)
            {
                    pmclient->reg->online_time+=now-pmclient->reg->last_time;
                    pmclient->reg->last_time=now;
                    pmclient->reg->status=USER_STATUS_OFFLINE;
                    pmclient->reg->user=NULL;

                    save_userreg(pmclient->reg);
            }

            pmclient=pmclient->next;
    }

    /*Rotina identica a' de cima para canais*/

    close(*gplisten_s);
}
